// Copyright (c) 2011, Marc-Andre Ferland - madrang@gmail.com
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the The Warrent Team nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//     * This software is free for non-commercial use. You may not use this
//       software, in whole or in part, in support of any commercial product
//       without the express consent of the author.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE WARRENT TEAM BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using OsHelper.UsbNet;
using OsHelper.UsbNet.Descriptors;

namespace UsbNUnit
{
	/// <summary>
	/// Device used to test the abstract UsbDevice Class.
	/// </summary>
	public class TestDevice : OsHelper.UsbNet.UsbDevice
	{
		public bool InternalIsOpen = false;
		public byte InternalConfiguration = 0;
		public byte InternalBusNumber = 0;
		public byte InternalDeviceAddress = 0;
		
		public DeviceDescriptor InternalDeviceDescriptor;
		
		private Dictionary<int, Dictionary<LanguageIdentifiers, string>> dic_StringDictionary;
		private List<LanguageIdentifiers> ls_LangList;
		
		public TestDevice () : base()
		{
			this.InternalDeviceDescriptor = new DeviceDescriptor();
			this.ls_LangList = new List<LanguageIdentifiers>();
			this.dic_StringDictionary = new Dictionary<int, Dictionary<LanguageIdentifiers, string>>();
		}
		
		public void AddDictionary(int Index, Dictionary<LanguageIdentifiers, string> Strings)
		{
			this.dic_StringDictionary.Add(Index, Strings);
			
			foreach (KeyValuePair<LanguageIdentifiers, string> Item in Strings) {
				if(!this.ls_LangList.Contains(Item.Key))
					this.ls_LangList.Add(Item.Key);
			}
		}
		
		#region Method
		
		public override void Open()
		{
			if(!this.IsOpen) {
				this.InternalIsOpen = true;
			}
		}
		
		public override void Close()
		{
			if(this.IsOpen) {
				this.InternalIsOpen = false;
			}
		}
		
		public override void Dispose()
		{
			this.Close();
		}
		
		public override void ResetDevice ()
		{
			this.CheckOpen("Can't reset a closed device.");
		}
		
		public override void SetAlternateInterface (int InterfaceID, byte AltInterfaceID)
		{
			throw new NotImplementedException ();
		}
		
		public override byte GetAlternateInterface (byte InterfaceID)
		{
			throw new NotImplementedException ();
		}
		
		public override void ClaimInterface (int InterfaceID)
		{
			this.CheckOpen("Can't claim interface of a closed device.");
			
			base.ClaimInterface(InterfaceID);
		}
		
		public override void ReleaseInterface (int InterfaceID)
		{
			this.CheckOpen("Can't release interface of a closed device.");
			
			base.ReleaseInterface(InterfaceID);
		}
		
		public override int Transfer(EndpointDescriptor Endpoint, byte[] Data, int Length, int Timeout)
		{
			this.CheckOpen("Can't receive data while device is not open.");
			
			throw new NotImplementedException ();
		}
		
		public override int ControlTransfer (RequestAttributes MyAttributes, byte RequestType, short Value, short Index, byte[] Data, short Length, int Timeout)
		{
			this.CheckOpen("Can't receive data while device is not open.");
			
			byte DeviceToHost = (byte)(new RequestAttributes(Recipient.Device, TransferDirection.DeviceToHost, OsHelper.UsbNet.Type.Standard));
			byte HostToDevice = (byte)(new RequestAttributes(Recipient.Device, TransferDirection.HostToDevice, OsHelper.UsbNet.Type.Standard));
			
			if((byte)MyAttributes == DeviceToHost)
				return this.DeviceToHostRequest(RequestType, Value, Index, Data, Length, Timeout);
			
			if((byte)MyAttributes == HostToDevice)
				return this.HostToDeviceRequest(RequestType, Value, Index, Data, Length, Timeout);
			
			throw new PipeErrorException();
		}
		
		public int DeviceToHostRequest(byte RequestType, short Value, short Index, byte[] Data, short Length, int Timeout)
		{
			switch (RequestType) {
			case (byte)StandardRequestsType.GetDescriptor:
				return this.GetDescriptor(Value, Index, Data, Length, Timeout);
			case (byte)StandardRequestsType.GetConfiguration:
				
				if(Value != 0 || Index != 0)
					throw new PipeErrorException();
				
				if(Data.Length < 1 || Length < 1)
					return 0;
				
				Data[0] = this.InternalConfiguration;
				return 1;
			default:
				throw new PipeErrorException();
			}
		}
		
		public int HostToDeviceRequest(byte RequestType, short Value, short Index, byte[] Data, short Length, int Timeout)
		{
			switch (RequestType) {
			case (byte)StandardRequestsType.SetConfiguration:
				
				if(Index != 0 || (Data != null && Data.Length != 0) || Length != 0)
					throw new PipeErrorException();
				
				this.InternalConfiguration = (byte)Value;
				return 1;
			default:
				throw new PipeErrorException();
			}
		}
		
		public int GetDescriptor(short Value, short Index, byte[] Data, short Length, int Timeout)
		{
			DescriptorType DeviceType = new DescriptorType(StandardDescriptorSubType.Device);
			DescriptorType ConfigurationType = new DescriptorType(StandardDescriptorSubType.Configuration);
			DescriptorType StringType = new DescriptorType(StandardDescriptorSubType.String);
			
			byte DescType = (byte)(Value >> 8);
			byte DescIndex = (byte)Value;
			
			if(DescType == (byte)DeviceType) {
				if(DescIndex != 0 || Index != 0)
					throw new PipeErrorException();
				
				if(Data.Length < Length)
					throw new OutOfMemoryException();
				
				if(Data.Length < 1 || Length < 1)
					return 0;
				
				byte[] TmpData = this.InternalDeviceDescriptor.ToBytes();
				int ToCopy = Math.Min(TmpData.Length, (int)Length);
				Array.Copy(TmpData, 0, Data, 0, ToCopy);
				return ToCopy;
			
			} else if(DescType == (byte)ConfigurationType) {
				throw new NotImplementedException ();
			
			} else if(DescType == (byte)StringType) {
				if(Data.Length < Length)
					throw new OutOfMemoryException();
				
				if(Data.Length < 1 || Length < 1)
					return 0;
				
				if(DescIndex == 0) {
					LanguageIdentifiersDescriptor LangIDDesc = new LanguageIdentifiersDescriptor();
					LangIDDesc.LanguageIdentifiers = this.ls_LangList;
					
					byte[] TmpData = LangIDDesc.ToBytes();
					int ToCopy = Math.Min(TmpData.Length, (int)Length);
					Array.Copy(TmpData, 0, Data, 0, ToCopy);
					
					return ToCopy;
				}
				
				Dictionary<LanguageIdentifiers, string> LangStringDic;
				
				if(!this.dic_StringDictionary.TryGetValue(DescIndex, out LangStringDic))
					throw new PipeErrorException();
				
				string s = string.Empty;
				
				if(!LangStringDic.TryGetValue((LanguageIdentifiers)Index, out s))
					throw new PipeErrorException();
				
				StringDescriptor StrDesc = new StringDescriptor();
				StrDesc.Value = s;
				
				byte[] StrData = StrDesc.ToBytes();
				int StrToCopy = Math.Min(StrData.Length, (int)Length);
				Array.Copy(StrData, 0, Data, 0, StrToCopy);
				
				return StrToCopy;
			}
			
			throw new PipeErrorException();
		}
		
		#endregion
		
		#region Properties
		
		public override byte BusNumber {
			get {
				return this.InternalBusNumber;
			}
		}
		
		public override byte DeviceAddress {
			get {
				return this.InternalDeviceAddress;
			}
		}
		
		public override bool IsOpen {
			get {
				return this.InternalIsOpen;
			}
		}
		
		#endregion
		
		public static List<UsbDevice> LibUsbDevices {
			get {
				throw new NotImplementedException ();
			}
		}
		
	}
	
	[TestFixture()]
	public class UsbDeviceNUnit
	{
		[Test()]
		public void GetDeviceInfo ()
		{
			TestDevice DevTest = new TestDevice();
			DevTest.InternalDeviceDescriptor.VendorID = 0xAF2F;
			DevTest.InternalDeviceDescriptor.ProductID = 0xC1B3;
			
			DevTest.Open();
			
			Assert.AreEqual(0xAF2F, DevTest.DeviceDescriptor.VendorID);
			Assert.AreEqual(0xC1B3, DevTest.DeviceDescriptor.ProductID);
		}
		
		[Test()]
		public void GetLangID()
		{
			Dictionary<LanguageIdentifiers, string> LangStringDic = new Dictionary<LanguageIdentifiers, string>();
			LangStringDic.Add(LanguageIdentifiers.EnglishUnitedStates, "The NUnit Test is Working.");
			LangStringDic.Add(LanguageIdentifiers.FrenchCanadian, "Le test NUnit est fonctionel.");
			
			TestDevice DevTest = new TestDevice();
			DevTest.AddDictionary(5, LangStringDic);
			
			DevTest.Open();
			
			List<LanguageIdentifiers> LangIDs = new List<LanguageIdentifiers>(DevTest.LanguageDescriptor.LanguageIdentifiers);
			
			Assert.AreEqual(2, LangIDs.Count);
			Assert.IsTrue(LangIDs.Contains(LanguageIdentifiers.EnglishUnitedStates));
			Assert.IsTrue(LangIDs.Contains(LanguageIdentifiers.FrenchCanadian));
		}
		
		[Test()]
		public void GetString()
		{
			Dictionary<LanguageIdentifiers, string> LangStringDic = new Dictionary<LanguageIdentifiers, string>();
			LangStringDic.Add(LanguageIdentifiers.EnglishUnitedStates, "The NUnit Test is Working.");
			LangStringDic.Add(LanguageIdentifiers.FrenchCanadian, "Le test NUnit est fonctionel.");
			
			TestDevice DevTest = new TestDevice();
			DevTest.AddDictionary(5, LangStringDic);
			
			DevTest.Open();
			
			string Result;
			Result = DevTest.GetString(5, LanguageIdentifiers.EnglishUnitedStates);
			Assert.AreEqual("The NUnit Test is Working.", Result);
			
			Result = DevTest.GetString(5, LanguageIdentifiers.FrenchCanadian);
			Assert.AreEqual("Le test NUnit est fonctionel.", Result);
		}
		
		#region Interface
		
		[Test()]
		public void SetAlternateInterface ()
		{
			throw new NotImplementedException();
		}

		[Test()]
		public void GetAlternateInterface ()
		{
			throw new NotImplementedException();
		}
		
		[Test()]
		public void ClaimInterface ()
		{
			throw new NotImplementedException();
		}
		
		[Test()]
		public void ReleaseInterface ()
		{
			throw new NotImplementedException();
		}
		
		#endregion
		
		#region Configuration
		
		[Test(Description = "Get the currently active configuration value.")]
		public void GetActiveConfigurationValue ()
		{
			TestDevice DevTest = new TestDevice();
			DevTest.InternalConfiguration = 3;
			
			DevTest.Open();
			
			Assert.AreEqual(3, DevTest.Configuration);
		}
		
		[Test()]
		public void SetActiveConfigurationValue ()
		{
			TestDevice DevTest = new TestDevice();
			DevTest.InternalConfiguration = 0;
			
			DevTest.Open();
			
			DevTest.Configuration = 4;
			Assert.AreEqual(4, DevTest.InternalConfiguration);
		}
		
		[Test()]
		public void IsConfigured ()
		{
			/* A zero value means the device is not configured and
			 * a non-zero value indicates the device is configured.
			 * */
			TestDevice DevTest = new TestDevice();
			DevTest.InternalConfiguration = 0;
			
			DevTest.Open();
			
			Assert.AreEqual(false, DevTest.IsConfigured);
			
			DevTest.InternalConfiguration = 3;
			
			Assert.AreEqual(true, DevTest.IsConfigured);
		}
		
		[Test()]
		public void GetConfigurationDescriptor ()
		{
			throw new NotImplementedException();
		}
		
		[Test()]
		public void ConfigurationIndexByValue ()
		{
			throw new NotImplementedException();
		}
		
		[Test()]
		public void ActiveConfigurationDescriptor ()
		{
			throw new NotImplementedException();
		}
		
		[Test()]
		public void ConfigurationDescriptorList ()
		{
			throw new NotImplementedException();
		}
		
		#endregion
		
	}
}
